﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Globalization;
using Tomahawk.Resources.Reflection;

namespace Tomahawk.Resources.Serialization
{
    /// <summary>
    /// Base class for all serializers. Includes utility methods for fields serializtion
    /// </summary>
    public abstract class BoostSerializer : IBoostSerializer
    {
        /// <summary>
        /// Fully qualified name of the type this boost serializers is able to work for
        /// </summary>
        public abstract string TypeName { get; }

        /// <summary>
        /// Short name qualified name of the type this boost serializers is able to work for
        /// </summary>
        public abstract string ShortTypeName { get; }

        /// <summary>
        /// Override to return a new instance of the serializable type
        /// </summary>
        public abstract object CreateInstance();

        /// <summary>
        /// Override this to provide the serialization mechanism for this type of objects
        /// </summary>
        public abstract void Serialize(object obj2Serialize, XmlWriter xmlW);
        
        /// <summary>
        /// Override this to provide the serialization mechanism for this type of objects
        /// </summary>
        public abstract void SerializeInner(object obj2Serialize, XmlWriter xmlW);

        /// <summary>
        /// Utility variable to deserialize enum types
        /// </summary>
        protected int enumTemp = -1;

        /// <summary>
        /// Common implementation of desarialization of complete object
        /// </summary>
        public object Deserialize(XmlReader xmlR)
        {
            object instance = this.CreateInstance();

            this.readOpenObject(xmlR);
            this.DeserializeInner(xmlR, instance);
            this.readCloseObject(xmlR);

            return instance;
        }


        public abstract void DeserializeInner(XmlReader xmlR, object targetObj);

        #region "Write Values"

        #region "Object Header element"

        /// <summary>
        /// Write the opening for this element type
        /// </summary>
        protected void writeOpenObject(XmlWriter xmlW)
        {
            //write the type name
            xmlW.WriteStartElement(this.ShortTypeName);
            this.writeStringAttribute(xmlW, "type", this.TypeName);
        }

        /// <summary>
        /// Write the ending for this element type
        /// </summary>
        protected void writeCloseObject(XmlWriter xmlW)
        {
            xmlW.WriteEndElement();
        }

        #endregion

        #region "Attributes"

        /// <summary>
        /// Write an string attribute
        /// </summary>
        protected void writeStringAttribute(XmlWriter xmlW, string attributeName, string attributeValue)
        {
            //write the field
            xmlW.WriteStartAttribute(attributeName);
            xmlW.WriteValue(attributeValue);
            xmlW.WriteEndAttribute();
        }
        
        /// <summary>
        /// Write a bool attribute
        /// </summary>
        protected void writeBoolAttribute(XmlWriter xmlW, string attributeName, bool attributeValue)
        {
            //write the field
            xmlW.WriteStartAttribute(attributeName);
            xmlW.WriteValue(attributeValue);
            xmlW.WriteEndAttribute();
        }

        protected void writeFloatAttribute(XmlWriter xmlW, string attributeName, float attributeValue)
        {
            string sValue = attributeValue.ToString(CultureInfo.InvariantCulture);

            //write the field
            xmlW.WriteStartAttribute(attributeName);
            xmlW.WriteValue(sValue);
            xmlW.WriteEndAttribute();
        }

        protected void writeIntAttribute(XmlWriter xmlW, string attributeName, int value)
        {
            //write the field
            xmlW.WriteStartAttribute(attributeName);
            xmlW.WriteValue(value);
            xmlW.WriteEndAttribute();
        }
        #endregion

        #region "Elements"

        protected void writeStringElement(XmlWriter xmlW, string elementName, string value)
        {
            //write the field
            xmlW.WriteStartElement(elementName);
            xmlW.WriteValue(value);
            xmlW.WriteEndElement();
        }

        protected void writeBoolElement(XmlWriter xmlW, string elementName, bool value)
        {
            //write the field
            xmlW.WriteStartElement(elementName);
            xmlW.WriteValue(value);
            xmlW.WriteEndElement();
        }
        
        protected void writeIntElement(XmlWriter xmlW, string elementName, int value)
        {
            //write the field
            xmlW.WriteStartElement(elementName);
            xmlW.WriteValue(value);
            xmlW.WriteEndElement();
        }

        protected void writeEnumElement(XmlWriter xmlW, string elementName, Type enumType, object value)
        {
            //transform the enum into string
            string enumString = value.ToString();

            //write the field
            xmlW.WriteStartElement(elementName);
            xmlW.WriteValue(enumString);
            xmlW.WriteEndElement();
        }

        protected void writeSerializableListElement(XmlWriter xmlW, string elementName, IXmlSerializable list)
        {
            //write start element
            xmlW.WriteStartElement(elementName);

            //pass on control to the serializable list, that will write each object
            list.WriteXml(xmlW);

            //write closing element
            xmlW.WriteEndElement();
        }

        protected void writeClassElement(XmlWriter xmlW, string elementName, object value)
        {
            xmlW.WriteStartElement(elementName);

            IBoostSerializer bs = SerialBoost.Instance.getBoostSerializer(value.GetType());
            if (bs != null)
            {
                //write the type atribute
                xmlW.WriteAttributeString("type", bs.TypeName);

                //write the rest of the object
                bs.SerializeInner(value, xmlW);
            }

            xmlW.WriteEndElement();
        }

        protected void writeStructElementBegin(XmlWriter xmlW, string elementName)
        {
            xmlW.WriteStartElement(elementName);
        }

        protected void writeStructElementEnd(XmlWriter xmlW)
        {
            xmlW.WriteEndElement();
        }

        #endregion

        #endregion

        #region "Read Values"

        private void readToElement(XmlReader xmlR)
        {
            while(xmlR.NodeType != XmlNodeType.Element) { xmlR.Read(); }
        }

        #region "Object Header element"

        /// <summary>
        /// Write the opening for this element type
        /// </summary>
        protected void readOpenObject(XmlReader xmlR)
        {
            this.readToElement(xmlR);

            xmlR.MoveToFirstAttribute();
            string sType = xmlR.Value;

            if (sType != this.TypeName)
            {
                throw new Exception();
            }
        }

        /// <summary>
        /// Write the ending for this element type
        /// </summary>
        protected void readCloseObject(XmlReader xmlR)
        {
            xmlR.ReadEndElement();
        }

        #endregion

        #region "Attributes"

        protected void readIntAttribute(XmlReader xmlR, string attributeName, out int target)
        {
            string sValue = xmlR.GetAttribute(attributeName);
            target = int.Parse(sValue, CultureInfo.InvariantCulture);
        }

        protected void readStringAttribute(XmlReader xmlR, string attributeName, out string target)
        {
            target = xmlR.GetAttribute(attributeName);
        }

        protected void readFloatAttribute(XmlReader xmlR, string attributeName, out float target)
        {
            string sValue = xmlR.GetAttribute(attributeName);
            target = float.Parse(sValue, CultureInfo.InvariantCulture);
        }

        protected void readBoolAttribute(XmlReader xmlR, string attributeName, out bool target)
        {
            string sValue = xmlR.GetAttribute(attributeName);
            target = bool.Parse(sValue);
        }

        protected void readEnumAttribute(XmlReader xmlR, string attributeName, Type enumType, out object target)
        {
            string sValue = xmlR.GetAttribute(attributeName);
            target = Enum.Parse(enumType, sValue, false);
        }

        protected void readEndAttributes(XmlReader xmlR)
        {
            xmlR.MoveToNextAttribute();
        }

        #endregion

        #region "Elements"

        protected void readEnumElement(XmlReader xmlR, Type enumType, out int target)
        {
            if (xmlR.NodeType != XmlNodeType.Element) this.readToElement(xmlR);
            string sValue = xmlR.ReadElementContentAsString();
            target = (int)Enum.Parse(enumType, sValue, false);
        }

        protected void readStringElement(XmlReader xmlR, out string target)
        {
            if (xmlR.NodeType != XmlNodeType.Element) this.readToElement(xmlR);
            target = xmlR.ReadElementContentAsString();
        }

        protected void readIntElement(XmlReader xmlR, out int target)
        {
            if (xmlR.NodeType != XmlNodeType.Element) this.readToElement(xmlR);
            string sValue = xmlR.ReadElementContentAsString();
            target = int.Parse(sValue, CultureInfo.InvariantCulture);
        }

        protected void readFloatElement(XmlReader xmlR, out float target)
        {
            if (xmlR.NodeType != XmlNodeType.Element) this.readToElement(xmlR);
            string sValue = xmlR.ReadElementContentAsString();
            target = float.Parse(sValue, CultureInfo.InvariantCulture);
        }

        protected void readBoolElement(XmlReader xmlR, out bool target)
        {
            if (xmlR.NodeType != XmlNodeType.Element) this.readToElement(xmlR);
            string sValue = xmlR.ReadElementContentAsString();
            target = bool.Parse(sValue);
        }

        protected object readClassElement(XmlReader xmlR)
        {
            object retVal = null;
            if (xmlR.NodeType != XmlNodeType.Element) this.readToElement(xmlR);

            xmlR.MoveToFirstAttribute();
            string sType = xmlR.Value;

            if (sType != "")
            {
                Type tType = AssemblyIndexer.Instance.GetTypeFromAssemblies(sType);
                IBoostSerializer bs = SerialBoost.Instance.getBoostSerializer(tType);
                if (bs != null)
                {
                    retVal = bs.CreateInstance();
                    bs.DeserializeInner(xmlR, retVal);
                }
            }

            return retVal;
        }

        #endregion

        #region "Struct"

        protected void readStructElementBegin(XmlReader xmlR)
        {
            if (xmlR.NodeType != XmlNodeType.Element) this.readToElement(xmlR);
            xmlR.MoveToFirstAttribute();
        }

        protected void readStructElementEnd(XmlReader xmlR)
        {
            xmlR.ReadEndElement();
        }

        #endregion

        #endregion
    }

}
